/******************************************************************************
*	device/hub.c
*	 by Alex Chadwick
*
*	A light weight implementation of the USB protocol stack fit for a simple
*	driver.
*
*	device/hub.c contains code relating to the generic USB driver's hubs. USB
*	is designed such that this driver's interface would be virtually the same
*	across all systems, and in fact its implementation varies little either.
******************************************************************************/
#include <device/hub.h>
#include <hcd/hcd.h>
#include <platform/platform.h>
#include <usbd/device.h>
#include <usbd/devicerequest.h>
#include <usbd/descriptors.h>
#include <usbd/pipe.h>
#include <usbd/usbd.h>

#define ControlMessageTimeout 10

//+=============================================================================
// Func: HubLoad
// Desc:
// Args: -none-
// Retn: -none-
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
void  HubLoad (void)
{
	LOG_DEBUG("CSUD: Hub driver version 0.1\n");
	InterfaceClassAttach[InterfaceClassHub] = HubAttach;

	return;
}

//+=============================================================================
// Func: HubReadDescriptor
// Desc:
// Args: UsbDevice* device ..
// Retn: Result ..
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubReadDescriptor (UsbDevice* device)
{
	UsbDescriptorHeader  header;
	Result               result;

	HubDescriptor        *hubDesc = ((HubDevice*)device->DriverData)->Descriptor;

	result = UsbGetDescriptor(device, Hub, 0, 0, &header,
	                          sizeof(header), sizeof(header), 0x20);
	if (result != OK) {
		LOGF("HUB: Failed to read hub descriptor for %s.\n",
		     UsbGetDescription(device));
		return result;
	}

	// Ensure there is memory allocated for the hub descriptor
	if (hubDesc == NULL)  hubDesc = MemoryAllocate(header.DescriptorLength) ;
	if (hubDesc == NULL) {
		LOGF("HUB: Not enough memory to read hub descriptor for %s.\n",
		     UsbGetDescription(device));
		return ErrorMemory;
	}

	result = UsbGetDescriptor(device, Hub, 0, 0, hubDesc,
	                          header.DescriptorLength, header.DescriptorLength, 0x20);
	if (result != OK) {
		LOGF("HUB: Failed to read hub descriptor for %s.\n", UsbGetDescription(device));
		return result;
	}

	return OK;
}

//+=============================================================================
// Func: HubGetStatus
// Desc:
// Args: UsbDevice* device ..
// Retn: Result ..
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubGetStatus (UsbDevice* device)
{
	Result result;

	result = UsbControlMessage(
	         	device,
	         	(UsbPipeAddress) {
	         		.Type      = Control,
	         		.Speed     = device->Speed,
	         		.EndPoint  = 0 ,
	         		.Device    = device->Number,
	         		.Direction = In,
	         		.MaxSize   = SizeFromNumber(device->Descriptor.MaxPacketSize0),
	         	},
	         	&((HubDevice*)device->DriverData)->Status,
	         	sizeof(HubFullStatus),
	         	&(UsbDeviceRequest) {
	         		.Request = GetStatus,
	         		.Type    = 0xa0,
	         		.Length  = sizeof(HubFullStatus),
	         	},
	         	ControlMessageTimeout);

	if (result != OK)  return result ;

	if (device->LastTransfer < sizeof(HubFullStatus)) {
		LOGF("HUB: Failed to read hub status for %s.\n", UsbGetDescription(device));
		return ErrorDevice;
	}
	return OK;
}

//+=============================================================================
// Func: HubPortGetStatus
// Desc:
// Args: UsbDevice* device ..
//     : u8         port   ..
// Retn: Result ..
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubPortGetStatus (UsbDevice* device, u8 port)
{
	Result result;

	result = UsbControlMessage(
	         	device,
	         	(UsbPipeAddress) {
	         		.Type      = Control,
	         		.Speed     = device->Speed,
	         		.EndPoint  = 0,
	         		.Device    = device->Number,
	         		.Direction = In,
	         		.MaxSize   = SizeFromNumber(device->Descriptor.MaxPacketSize0),
	         	},
	         	&((HubDevice*)device->DriverData)->PortStatus[port],
	         	sizeof(HubPortFullStatus),
	         	&(UsbDeviceRequest) {
	         		.Request = GetStatus,
	         		.Type    = 0xa3,
	         		.Index   = port + 1,
	         		.Length  = sizeof(HubPortFullStatus),
	         	},
	         	ControlMessageTimeout);
	if (result != OK)  	return result ;

	if (device->LastTransfer < sizeof(HubPortFullStatus)) {
		LOGF("HUB: Failed to read hub port status for %s.Port%d.\n",
		     UsbGetDescription(device), port + 1);
		return ErrorDevice;
	}
	return OK;
}

//+=============================================================================
// Func: HubChangeFeature
// Desc:
// Args: UsbDevice* device  ..
//     : HubFeature feature ..
//     : bool       set     ..
// Retn: Result ..
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubChangeFeature (UsbDevice* device,
                          HubFeature feature, bool set)
{
	Result result;

	result = UsbControlMessage(
	         	device,
	         	(UsbPipeAddress) {
	         		.Type      = Control,
	         		.Speed     = device->Speed,
	         		.EndPoint  = 0,
	         		.Device    = device->Number,
	         		.Direction = Out,
	         		.MaxSize   = SizeFromNumber(device->Descriptor.MaxPacketSize0),
	         	},
	         	NULL,
	         	0,
	         	&(UsbDeviceRequest) {
	         		.Request = set ? SetFeature : ClearFeature,
	         		.Type    = 0x20,
	         		.Value   = (u8)feature,
	         	},
	         	ControlMessageTimeout);

	return result;
}

//+=============================================================================
// Func: HubChangePortFeature
// Desc:
// Args: UsbDevice*     device  ..
//     : HubPortFeature feature ..
//     : u8             port    ..
//     : bool           set     ..
// Retn: Result ..
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubChangePortFeature (UsbDevice* device,
                              HubPortFeature feature, u8 port, bool set)
{
	Result result;

	result = UsbControlMessage(
	         	device,
	         	(UsbPipeAddress) {
	         		.Type      = Control,
	         		.Speed     = device->Speed,
	         		.EndPoint  = 0,
	         		.Device    = device->Number,
	         		.Direction = Out,
	         		.MaxSize   = SizeFromNumber(device->Descriptor.MaxPacketSize0),
	         	},
	         	NULL,
	         	0,
	         	&(UsbDeviceRequest) {
	         		.Request = set ? SetFeature : ClearFeature,
	         		.Type    = 0x23,
	         		.Value   = (u16)feature,
	         		.Index   = port + 1,
	         	},
	         	ControlMessageTimeout);

	return result;
}

//+=============================================================================
// Func: HubPowerOn
// Desc:
// Args: UsbDevice* device ..
// Retn: Result ..
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubPowerOn (UsbDevice* device)
{
	u32            i;
	HubDevice      *data;
	HubDescriptor  *hubDescriptor;

	data = (HubDevice*)device->DriverData;
	hubDescriptor = data->Descriptor;
	LOG_DEBUGF("HUB: Powering on hub %s.\n", UsbGetDescription(device));

	for (i = 0; i < data->MaxChildren; i++)
		if (HubChangePortFeature(device, FeaturePower, i, true) != OK)
			LOGF("HUB: Could not power %s.Port%d.\n", UsbGetDescription(device), i + 1);

	MicroDelay(hubDescriptor->PowerGoodDelay * 2000);

	return OK;
}

//+=============================================================================
// Func: HubPortReset
// Desc: Resets a port on a hub.
// Args: UsbDevice* device ..
//     : u8         port   ..
// Retn: Result ..
// Note: No validation.
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubPortReset (UsbDevice* device, u8 port)
{
	Result             result;
	HubDevice          *data;
	HubPortFullStatus  *portStatus;
	u32                retry,  timeout;

	data = (HubDevice*)device->DriverData;
	portStatus = &data->PortStatus[port];

	LOG_DEBUGF("HUB: Hub reset %s.Port%d.\n", UsbGetDescription(device), port + 1);
	for (retry = 0; retry < 3; retry++) {
		result = HubChangePortFeature(device, FeatureReset, port, true);
		if (result != OK) {
			LOGF("HUB: Failed to reset %s.Port%d.\n", UsbGetDescription(device), port + 1);
			return result;
		}
		timeout = 0;
		do {
			MicroDelay(20000);
			result = HubPortGetStatus(device, port);
			if (result != OK) {
				LOGF("HUB: Hub failed to get status (4) for %s.Port%d.\n",
				     UsbGetDescription(device), port + 1);
				return result;
			}
			timeout++;
		} while (!portStatus->Change.ResetChanged &&
		         !portStatus->Status.Enabled      && (timeout < 10));

		if (timeout == 10)  continue ;

		LOG_DEBUGF("HUB: %s.Port%d Status %x:%x.\n", UsbGetDescription(device),
		           port + 1, *(u16*)&portStatus->Status, *(u16*)&portStatus->Change);

		if (portStatus->Change.ConnectedChanged || !portStatus->Status.Connected)
			return ErrorDevice;

		if (portStatus->Status.Enabled)  break ;
	}

	if (retry == 3) {
		LOGF("HUB: Cannot enable %s.Port%d. Please verify the hardware is working.\n",
		     UsbGetDescription(device), port + 1);
		return ErrorDevice;
	}

	result = HubChangePortFeature(device, FeatureResetChange, port, false);
	if (result != OK) {
		LOGF("HUB: Failed to clear reset on %s.Port%d.\n", UsbGetDescription(device), port + 1);
	}
	return OK;
}

//+=============================================================================
// Func: HubPortConnectionChanged
// Desc:
// Args: UsbDevice* device ..
//     : u8         port   ..
// Retn: Result ..
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubPortConnectionChanged (UsbDevice* device, u8 port)
{
	Result result;
	HubDevice *data;
	HubPortFullStatus *portStatus;

	data = (HubDevice*)device->DriverData;
	portStatus = &data->PortStatus[port];

	result = HubPortGetStatus(device, port);
	if (result != OK) {
		LOGF("HUB: Hub failed to get status (2) for %s.Port%d.\n",
		     UsbGetDescription(device), port + 1);
		return result;
	}
	LOG_DEBUGF("HUB: %s.Port%d Status %x:%x.\n", UsbGetDescription(device),
	           port + 1, *(u16*)&portStatus->Status, *(u16*)&portStatus->Change);

	result = HubChangePortFeature(device, FeatureConnectionChange, port, false);
	if (result != OK) {
		LOGF("HUB: Failed to clear change on %s.Port%d.\n", UsbGetDescription(device), port + 1);
	}

	if ((!portStatus->Status.Connected && !portStatus->Status.Enabled) || (data->Children[port] != NULL)) {
		LOGF("HUB: Disconnected %s.Port%d - %s.\n", UsbGetDescription(device),
		     port + 1, UsbGetDescription(data->Children[port]));
		UsbDeallocateDevice(data->Children[port]);
		data->Children[port] = NULL;
		if (!portStatus->Status.Connected)  return OK ;
	}

	result = HubPortReset(device, port);
	if (result != OK) {
		LOGF("HUB: Could not reset %s.Port%d for new device.\n",
		     UsbGetDescription(device), port + 1);
		return result;
	}

	result = UsbAllocateDevice(&data->Children[port]);
	if (result != OK) {
		LOGF("HUB: Could not allocate a new device entry for %s.Port%d.\n",
		     UsbGetDescription(device), port + 1);
		return result;
	}

	result = HubPortGetStatus(device, port);
	if (result != OK) {
		LOGF("HUB: Hub failed to get status (3) for %s.Port%d.\n",
		     UsbGetDescription(device), port + 1);
		return result;
	}

	LOG_DEBUGF("HUB: %s.Port%d Status %x:%x.\n", UsbGetDescription(device),
	           port + 1, *(u16*)&portStatus->Status, *(u16*)&portStatus->Change);

	if (portStatus->Status.HighSpeedAttatched)      data->Children[port]->Speed = High ;
	else if (portStatus->Status.LowSpeedAttatched)  data->Children[port]->Speed = Low ;
	else                                            data->Children[port]->Speed = Full ;
	data->Children[port]->Parent     = device;
	data->Children[port]->PortNumber = port;

	result = UsbAttachDevice(data->Children[port]);
	if (result != OK) {
		LOGF("HUB: Could not connect to new device in %s.Port%d. Disabling.\n",
		     UsbGetDescription(device), port + 1);
		UsbDeallocateDevice(data->Children[port]);
		data->Children[port] = NULL;
		if (HubChangePortFeature(device, FeatureEnable, port, false) != OK) {
			LOGF("HUB: Failed to disable %s.Port%d.\n", UsbGetDescription(device), port + 1);
		}
		return result;
	}
	return OK;
}

//+=============================================================================
// Func: HubDetached
// Desc:
// Args: UsbDevice* device ..
// Retn: -none-
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
void  HubDetached (UsbDevice* device)
{
	u32        i;
	HubDevice  *data;

	LOG_DEBUGF("HUB: %s detached.\n", UsbGetDescription(device));
	if (device->DriverData != NULL) {
		data = (HubDevice*)device->DriverData;

		for (i = 0; i < data->MaxChildren; i++)
			if ((data->Children[i] != NULL) &&
			    (data->Children[i]->DeviceDetached != NULL))
				data->Children[i]->DeviceDetached(data->Children[i]);
	}

	return;
}

//+=============================================================================
// Func: HubDeallocate
// Desc:
// Args: UsbDevice* device ..
// Retn: -none-
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
void  HubDeallocate (UsbDevice* device)
{
	u32        i;
	HubDevice  *data;

	LOG_DEBUGF("HUB: %s deallocate.\n", UsbGetDescription(device));
	if (device->DriverData != NULL) {
		data = (HubDevice*)device->DriverData;

		for (i = 0; i < data->MaxChildren; i++)
			if (data->Children[i] != NULL) {
				UsbDeallocateDevice(data->Children[i]);
				data->Children[i] = NULL;
			}

		if (data->Descriptor != NULL)  MemoryDeallocate(data->Descriptor) ;
		MemoryDeallocate((void*)device->DriverData);
	}
	device->DeviceDeallocate      = NULL;
	device->DeviceDetached        = NULL;
	device->DeviceCheckForChange  = NULL;
	device->DeviceChildDetached   = NULL;
	device->DeviceChildReset      = NULL;
	device->DeviceCheckConnection = NULL;

	return;
}

//+=============================================================================
// Func: HubCheckForChange
// Desc:
// Args: UsbDevice* device ..
// Retn: -none-
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
void  HubCheckForChange (UsbDevice* device)
{
	u32        i;
	HubDevice  *data;

	data = (HubDevice*)device->DriverData;

	for (i = 0; i < data->MaxChildren; i++) {
		if (HubCheckConnection(device, i) != OK)  continue ;

		if ((data->Children[i] != NULL) &&
			(data->Children[i]->DeviceCheckForChange != NULL))
				data->Children[i]->DeviceCheckForChange(data->Children[i]);
	}

	return;
}

//+=============================================================================
// Func: HubChildDetached
// Desc:
// Args: UsbDevice* device ..
//     : UsbDevice* child  ..
// Retn: -none-
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
void  HubChildDetached (UsbDevice* device, UsbDevice* child)
{
	HubDevice *data;

	data = (HubDevice*)device->DriverData;

	if ((child->Parent == device) && (child->PortNumber >= 0) &&
	    (child->PortNumber < data->MaxChildren) &&
		(data->Children[child->PortNumber] == child))
		data->Children[child->PortNumber] = NULL;

	return;
}

//+=============================================================================
// Func: HubChildReset
// Desc:
// Args: UsbDevice* device ..
//     : UsbDevice* child  ..
// Retn: Result ..
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubChildReset (UsbDevice* device, UsbDevice* child)
{
	HubDevice *data;

	data = (HubDevice*)device->DriverData;

	if ((child->Parent == device) && (child->PortNumber >= 0) &&
	    (child->PortNumber < data->MaxChildren) &&
	    (data->Children[child->PortNumber] == child))
		return HubPortReset(device, child->PortNumber);
	else
		return ErrorDevice;
}

//+=============================================================================
// Func: HubCheckConnectionDevice
// Desc:
// Args: UsbDevice* device ..
//     : UsbDevice* child  ..
// Retn: Result ..
// Note:
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubCheckConnectionDevice (UsbDevice* device, UsbDevice* child)
{
	HubDevice  *data;
	Result     result;

	data = (HubDevice*)device->DriverData;

	if ((child->Parent == device) && (child->PortNumber >= 0) &&
	    (child->PortNumber < data->MaxChildren) &&
	    (data->Children[child->PortNumber] == child)) {
		result = HubCheckConnection(device, child->PortNumber);
		if (result != OK)  return result ;
		return (data->Children[child->PortNumber] == child) ? OK : ErrorDisconnected;

	} else {
		return ErrorArgument;
	}
}

//+=============================================================================
// Func: HubCheckConnection
// Desc: Checks for a change in the connection status of a port.
// Args: UsbDevice* device ..
//     : u8         port   ..
// Retn: Result ..
// Note: If it has changed, perform the necessary actions
//         such as enumerating a new device or deallocating an old one.
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubCheckConnection (UsbDevice* device, u8 port)
{
	Result result;
	HubPortFullStatus *portStatus;
	HubDevice *data;

	data = (HubDevice*)device->DriverData;

	result = HubPortGetStatus(device, port);
	if (result != OK) {
		if (result != ErrorDisconnected)
			LOGF("HUB: Failed to get hub port status (1) for %s.Port%d.\n",
			     UsbGetDescription(device), port + 1);
		return result;
	}

	portStatus = &data->PortStatus[port];
	if (portStatus->Change.ConnectedChanged) {
		HubPortConnectionChanged(device, port);
	}

	if (portStatus->Change.EnabledChanged) {
		if (HubChangePortFeature(device, FeatureEnableChange, port, false) != OK) {
			LOGF("HUB: Failed to clear enable change %s.Port%d.\n",
			     UsbGetDescription(device), port + 1);
		}

		// This may indicate EM interference.
		if (!portStatus->Status.Enabled && portStatus->Status.Connected && (data->Children[port] != NULL)) {
			LOGF("HUB: %s.Port%d has been disabled, but is connected. "
			     "This can be cause by interference. Reenabling!\n",
			     UsbGetDescription(device), port + 1);
			HubPortConnectionChanged(device, port);
		}
	}

	if (portStatus->Status.Suspended) {
		if (HubChangePortFeature(device, FeatureSuspend, port, false) != OK) {
			LOGF("HUB: Failed to clear suspended port - %s.Port%d.\n",
			     UsbGetDescription(device), port + 1);
		}
	}

	if (portStatus->Change.OverCurrentChanged) {
		if (HubChangePortFeature(device, FeatureOverCurrentChange, port, false) != OK) {
			LOGF("HUB: Failed to clear over current port - %s.Port%d.\n",
			     UsbGetDescription(device), port + 1);
		}
		HubPowerOn(device);
	}

	if (portStatus->Change.ResetChanged) {
		if (HubChangePortFeature(device, FeatureResetChange, port, false) != OK) {
			LOGF("HUB: Failed to clear reset port - %s.Port%d.\n",
			     UsbGetDescription(device), port + 1);
		}
	}

	return OK;
}

//+=============================================================================
// Func: HubAttach
// Desc: Performs all necesary hub related initialisation.
// Args: UsbDevice* device          ..
//     : u32        InterfaceNumber ..
// Retn: Result ..
// Note: Loads a hub device and enumerates its children.
// Auth: Chadderz; BlueChip
//==============================================================================
Result  HubAttach (UsbDevice* device, u32 interfaceNumber)
{
	u8             port;
	u32            i;
	Result         result;
	HubDevice      *data;
	HubDescriptor  *hubDescriptor;
	HubFullStatus  *status;

	if (device->Interfaces[interfaceNumber].EndpointCount != 1) {
		LOGF("HUB: Cannot enumerate hub with multiple endpoints: %d.\n",
		     device->Interfaces[interfaceNumber].EndpointCount);
		return ErrorIncompatible;
	}

	if (device->Endpoints[interfaceNumber][0].EndpointAddress.Direction == Out) {
		LOG("HUB: Cannot enumerate hub with only one output endpoint.\n");
		return ErrorIncompatible;
	}

	if (device->Endpoints[interfaceNumber][0].Attributes.Type != Interrupt) {
		LOG("HUB: Cannot enumerate hub without interrupt endpoint.\n");
		return ErrorIncompatible;
	}

	device->DeviceDeallocate      = HubDeallocate;
	device->DeviceDetached        = HubDetached;
	device->DeviceCheckForChange  = HubCheckForChange;
	device->DeviceChildDetached   = HubChildDetached;
	device->DeviceChildReset      = HubChildReset;
	device->DeviceCheckConnection = HubCheckConnectionDevice;
	device->DriverData            = MemoryAllocate(sizeof(HubDevice));
	if (device->DriverData == NULL) {
		LOG("HUB: Cannot allocate hub data. Out of memory.\n");
		return ErrorMemory;
	}

	data = (HubDevice*)device->DriverData;

	device->DriverData->DataSize     = sizeof(HubDevice);
	device->DriverData->DeviceDriver = DeviceDriverHub;
	for (i = 0; i < MaxChildrenPerDevice; i++)  data->Children[i] = NULL ;

	result = HubReadDescriptor(device);
	if (result != OK) return result;

	hubDescriptor = data->Descriptor;
	if (hubDescriptor->PortCount > MaxChildrenPerDevice) {
		LOGF("HUB: Hub %s is too big for this driver to handle. "
		     "Only the first %d ports will be used. "
		     "Change MaxChildrenPerDevice in usbd/device.h.\n",
		     UsbGetDescription(device), MaxChildrenPerDevice);
		data->MaxChildren = MaxChildrenPerDevice;
	} else {
		data->MaxChildren = hubDescriptor->PortCount;
	}

	switch (hubDescriptor->Attributes.PowerSwitchingMode) {
		case Global:
			LOG_DEBUG("HUB: Hub power: Global.\n");
			break;
		case Individual:
			LOG_DEBUG("HUB: Hub power: Individual.\n");
			break;
		default:
			LOGF("HUB: Unknown hub power type %d on %s. Driver incompatible.\n",
			     hubDescriptor->Attributes.PowerSwitchingMode, UsbGetDescription(device));
			HubDeallocate(device);
			return ErrorIncompatible;
	}

	if (hubDescriptor->Attributes.Compound)  LOG_DEBUG("HUB: Hub nature: Compound.\n") ;
	else                                     LOG_DEBUG("HUB: Hub nature: Standalone.\n") ;

	switch (hubDescriptor->Attributes.OverCurrentProtection) {
		case Global:
			LOG_DEBUG("HUB: Hub over current protection: Global.\n");
			break;
		case Individual:
			LOG_DEBUG("HUB: Hub over current protection: Individual.\n");
			break;
		default:
			LOGF("HUB: Unknown hub over current type %d on %s. Driver incompatible.\n",
			     hubDescriptor->Attributes.OverCurrentProtection, UsbGetDescription(device));
			HubDeallocate(device);
			return ErrorIncompatible;
	}

	LOG_DEBUGF("HUB: Hub power to good: %dms.\n",    hubDescriptor->PowerGoodDelay * 2);
	LOG_DEBUGF("HUB: Hub current required: %dmA.\n", hubDescriptor->MaximumHubPower * 2);
	LOG_DEBUGF("HUB: Hub ports: %d.\n",              hubDescriptor->PortCount);

#if DEBUG
	for (i = 0; i < data->MaxChildren; i++)
		if (hubDescriptor->Data[(i + 1) >> 3] & 1 << ((i + 1) & 0x7))
			LOG_DEBUGF("HUB: Hub port %d is not removable.\n", i + 1);
		else
			LOG_DEBUGF("HUB: Hub port %d is removable.\n", i + 1);
#endif

	result = HubGetStatus(device);
	if (result != OK) {
		LOGF("HUB: Failed to get hub status for %s.\n", UsbGetDescription(device));
		return result;
	}
	status = &data->Status;

	if (!status->Status.LocalPower)  LOG_DEBUG("USB Hub power: Good.\n") ;
	else                             LOG_DEBUG("HUB: Hub power: Lost.\n") ;

	if (!status->Status.OverCurrent)  LOG_DEBUG("USB Hub over current condition: No.\n") ;
	else                              LOG_DEBUG("HUB: Hub over current condition: Yes.\n") ;

	LOG_DEBUG("HUB: Hub powering on.\n");
	result = HubPowerOn(device);
	if (result != OK) {
		LOG_DEBUG("HUB: Hub failed to power on.\n");
		HubDeallocate(device);
		return result;
	}

	result = HubGetStatus(device);
	if (result != OK) {
		LOGF("HUB: Failed to get hub status for %s.\n", UsbGetDescription(device));
		HubDeallocate(device);
		return result;
	}

	if (!status->Status.LocalPower)  LOG_DEBUG("USB Hub power: Good.\n");
	else                             LOG_DEBUG("HUB: Hub power: Lost.\n");

	if (!status->Status.OverCurrent)  LOG_DEBUG("USB Hub over current condition: No.\n");
	else                              LOG_DEBUG("HUB: Hub over current condition: Yes.\n");

	LOG_DEBUGF("HUB: %s status %x:%x.\n", UsbGetDescription(device),
	           *(u16*)&status->Status, *(u16*)&status->Change);

	for (port = 0; port < data->MaxChildren; port++)
		HubCheckConnection(device, port);

	return OK;
}
